<?php

namespace RedMVC;

/**
 * DataTable class
 *
 * @category    RedMVC
 * @author      Jan Fischer, bitWorking <info@bitworking.de>
 */
class DataTable{

    // config
    public $id                  = '';
    public $baseUrl             = '';    
    public $fetchCallback       = false;
    public $numRowsCallback     = false;
    public $debugSql            = false;

    public $pageDefault         = 0;
    public $orderbyDefault      = '';
    public $sortDefault         = '';
    protected $perPageDefault   = 20;

    public $pageName            = 'page';
    public $orderbyName         = 'orderby';
    public $sortName            = 'sort';
    public $perPageName         = 'perpage';

    public $sortAscSymbol       = "&nbsp;&dArr;";
    public $sortDescSymbol      = "&nbsp;&uArr;";

    public $firstSymbol         = "<<";
    public $prevSymbol          = "<";
    public $nextSymbol          = ">";
    public $lastSymbol          = ">>";
    
    protected $_extraColumns    = array();
    protected $_columnCallbacks = array();
    protected $_extraRows       = array();
    protected $_filters         = array();
    protected $_filterEnabled   = false;
    protected $_uiEnabled       = false;
    protected $_totalColumns    = array();
    protected $_hiddenColumns   = array();
    
    // dynamic
    protected $page             = '';
    protected $perPage          = 0;
    protected $orderby          = '';
    protected $sort             = '';    
    protected $numRows          = 0;
    protected $totalPages       = 0;
    
    protected $keys             = array();
    protected $fetchResult      = array();

    public function __construct(){
        $db                     = Registry::get('RedMVC_Db');
        $this->fetchCallback    = array($db, 'fetch');
        $this->numRowsCallback  = array($db, 'count');
    }

    public function fetch($sql, $perPage = 20, $rowTemplateFunction = false){
        $this->perPageDefault = $perPage;

        if(!$this->runSql($sql)){            
            $this->_getKeysFromSql($sql);
        }

        if(!$rowTemplateFunction){
            return $this->standardTable();
        }

        return $this->parseRowTemplate($rowTemplateFunction);
    }

    protected function runSql($sql){
        // GET values
        $this->getUrlValues();
        $this->page = max($this->page, 0);
        $orderbyquery = '';
        $sortquery = '';
        $sort = '';
        if($this->orderby != ''){$orderbyquery = 'ORDER BY '.$this->orderby;}
        if($this->sort != ''){$sort = strtolower($this->sort);}
        if(($sort != 'asc')&&($sort != 'desc')){$sort='asc';}
        if($orderbyquery != ''){$sortquery = $sort;}
		
        // filter
        $filters = $this->getFilterValues();
        $sql = $this->insertFilterSql($sql, $filters);
        
        if($this->debugSql){
            echo $sql;
        }
	
        // total pages
        $total = $this->numRowsSql($sql);
        if($total == 0){         
            $this->page = -1;
            $this->totalPages = 0;
            return false;
        }
        
        if(null !== $this->perPage){
            $pages = floor($total/$this->perPage);        
            $rest = $total%$this->perPage;
            if($rest > 0){$pages = $pages+1;}
            $lastpage = $pages-1;
            if($this->page >= $lastpage){$this->page=$lastpage;}
            $this->totalPages = $pages;
         
            // query
            $start = ($this->page*$this->perPage);
            $sql = $sql." $orderbyquery $sortquery LIMIT $start,".$this->perPage."";
        }
        else{
            $sql = $sql." $orderbyquery $sortquery";
        }
		
        if($this->debugSql){
            echo '<br /><br />'.$sql;
            return;
        }
		
        $result = $this->fetchSql($sql);
   
        // keys
        $this->getKeys();

        return $result;
    }
    
    public function getSql(){
        return $this->_sql;
    }
    
    public function setUiEnabled($bool = true){
        $this->_uiEnabled = $bool;
        if($bool){
            $this->firstSymbol      = '<span class="ui-icon ui-icon-arrowstop-1-w"></span>';
            $this->prevSymbol       = '<span class="ui-icon ui-icon-arrow-1-w"></span>';
            $this->nextSymbol       = '<span class="ui-icon ui-icon-arrow-1-e"></span>';
            $this->lastSymbol       = '<span class="ui-icon ui-icon-arrowstop-1-e"></span>';
            //$this->sortAscSymbol    = '<span class="ui-icon ui-icon-arrowthick-1-s"></span>';
            //$this->sortDescSymbol   = '<span class="ui-icon ui-icon-arrowthick-1-n"></span>';
        }
        else{
            $this->firstSymbol      = '<<';
            $this->prevSymbol       = '<';
            $this->nextSymbol       = '>';
            $this->lastSymbol       = '>>';
        }
    }

    protected function standardTable(){
        $table = '<div class="dataTable">';
        if(null !== $this->perPage){
            if($this->_uiEnabled){
                $table .= '<div class="ui-toolbar ui-widget-header ui-corner-tl ui-corner-tr ui-helper-clearfix">';
            }            
            $table .= $this->getPerPageNavi();            
            if($this->_uiEnabled){
                $table .= "</div>";
            }
        }
        $table .= '<table border="0">';
        
        if(count($this->fetchResult) == 0){
            $translate = Registry::get('RedMVC_Translate');            
            $table .= '<thead>';
            $colspan = '';
            if(count($this->keys) > 0){
                $table .= $this->getHeadCols();
                $colspan = ' colspan="'.(count($this->keys) + count($this->_extraColumns) - count($this->_hiddenColumns)).'"';
            }            
            $table .= $this->getFilterRow();
            $uiClass = '';
            if($this->_uiEnabled){
                $uiClass = ' ui-widget-content';
            }
            $table .= '<tr><td class="dataTable_td1'.$uiClass.'"'.$colspan.'>';
            $table .= $translate->_('DATATABLE_NO_RESULT');            
            if($this->_filterEnabled && count($this->keys) == 0){
                $table .= '<a href="'.$this->filterResetUrl().'" style="margin-left:10px;">'.$translate->_('DATATABLE_FILTER_RESET').'</a>';
            }
            $table .= '</td></tr>';
            $table .= '</thead>';
        }
        else{
            // head
            $table .= '<thead><tr>';
            $table .= $this->getHeadCols();
            $table .= '</tr>';
            $table .= $this->getFilterRow();
            $table .= '</thead><tbody>';        
            
            $i = 1;
        
            foreach($this->fetchResult as $row=>$rowArray){
                $table .= "<tr valign='top'>";
                $replaceArray = array();
                foreach($rowArray as $key=>$value){                   
                    // column callback
                    if(isset($this->_columnCallbacks[$key])){
                        $value = call_user_func_array($this->_columnCallbacks[$key], array($value, $rowArray));
                    }
                    
                    $uiClass = '';
                    if($this->_uiEnabled){
                        if($i == 1){
                            $uiClass = ' ui-widget-content';
                        }
                        else{
                            $uiClass = ' ui-widget-header ui-state-hover';
                        }
                    }
                    if(!in_array($key, $this->_hiddenColumns)){
                        $table .= "<td class='dataTable_td".$i."".$uiClass."'>$value</td>";
                    }
                    $replaceArray['{'.$key.'}'] = $value;
                    $replaceArray['%7B'.$key.'%7D'] = $value;                
                }
                
                // extra columns
                foreach($this->_extraColumns as $key=>$value){
                    $value = strtr($value, $replaceArray);
                    $table .= "<td class='dataTable_td".$i."".$uiClass."'>".$value."</td>";
                }
                    
                $table .= "</tr>";
    
                // toggle
                if($i == 1){$i = 2;}
                else{$i = 1;}
            }
        }
        
        // extra rows
        $table .= $this->showExtraRows();
        // total row
        $table .= $this->showTotalRow();
        
        $table .= "</tbody></table>";
        
        $table .= $this->getPageNaviStandard();
        $table .= "</div>";        
        return $table;
    }
    
    public function getHeadCols(){
        $translate = Registry::get('RedMVC_Translate');
        $cols = '';
        $uiClass = '';
        if($this->_uiEnabled){
            $uiClass = ' ui-state-hover';
        }
        foreach($this->keys as $key){
            if(!in_array($key, $this->_hiddenColumns)){
                $cols .= "<th class='dataTable_th".$uiClass."'>".$this->link($translate->_($key).$this->getSortSymbol($key), $this->orderByUrl($key, "asc"))."</th>";
            }
        }        
        // extra columns
        foreach($this->_extraColumns as $key=>$value){
            $cols .= "<th class='dataTable_th".$uiClass."'>".$translate->_($key)."</th>";
        }        
        return $cols;
    }
    
    public function getFilterRow(){
        $cols = '';
        if(count($this->_filters) == 0){
            return '';
        }
        $showSubmit = false;
        
        $uiClass = '';
        if($this->_uiEnabled){
            $uiClass = ' ui-state-default';
        }
        foreach($this->keys as $key){
            if(in_array($key, $this->_hiddenColumns)){
                continue;
            }
            if(isset($this->_filters[$key])){
                if($this->_filters[$key]->fieldType === DataTable\Filter::FIELD_TYPE_TEXT){
                    $fieldName  = 'filter'.$this->id.'_'.$key;                                        
                    $fieldValue = $this->getUrlValue($fieldName, false, '');
                    $fieldValue = urldecode($fieldValue);                    
                    $field      = '<input type="text" name="'.$fieldName.'" value="'.$fieldValue.'" />';
                }
                else if($this->_filters[$key]->fieldType === DataTable\Filter::FIELD_TYPE_SELECT){
                    $fieldName  = 'filter'.$this->id.'_'.$key;
                    $fieldValue = $this->getUrlValue($fieldName, false, '');
                    $fieldValue = urldecode($fieldValue);
                    $field = $this->_getFilterSelect($fieldName, $fieldValue);
                }
                else{
                    $field = '';
                }                
                $cols .= '<th class="dataTable_th'.$uiClass.'">'.$field.'</th>';
                $showSubmit = true;
            }
            else{
                $cols .= '<th class="dataTable_th'.$uiClass.'">&nbsp;</th>';
            }
        }
        // extra columns
        foreach($this->_extraColumns as $key=>$value){
            $cols .= '<th class="dataTable_th'.$uiClass.'">&nbsp;</th>';
        }
        if($showSubmit){      
            $translate  = Registry::get('RedMVC_Translate');
            $mvcRequest = Registry::get('RedMVC_Request');
            $out = '<form method="get" action="">';            
            $out .= '<input type="hidden" name="controller" value="'.$mvcRequest->getControllerName().'" />';
            $out .= '<input type="hidden" name="action" value="'.$mvcRequest->getActionName().'" />';
            $out .= '<tr>'.$cols.'</tr>';        
            $colspan = count($this->keys) + count($this->_extraColumns) - count($this->_hiddenColumns);
            $out .= '<tr><th class="dataTable_th_filter'.$uiClass.'" colspan="'.$colspan.'">';
            $out .= '<input type="submit" value="'.$translate->_('DATATABLE_FILTER').'" style="float:left;" />';            
            $out .= '</form>';
            if($this->_filterEnabled){
                $out .= '<a href="'.$this->filterResetUrl().'" style="float:left;margin-left:10px;">'.$translate->_('DATATABLE_FILTER_RESET').'</a>';
            }
            $out .= '<div style="clear:both;"></div>';
            $out .= '</th></tr>';            
            return $out;
        }
        return '';
    }
    
    protected function _getFilterSelect($name, $value){
        $preString      = 'filter'.$this->id.'_';
        $preStringLen   = strlen($preString);
        $columnName     = substr($name, $preStringLen);
        $selectValues   = $this->_getFilterSelectValues($columnName);
        $out = '<select name="'.$name.'">';
        $translate = Registry::get('RedMVC_Translate');
        $out .= '<option value="">'.$translate->_('DATATABLE_FILTER_SELECT_ALL').'</option>';
        foreach($selectValues as $selectValue){
            $selected = '';
            if($value == $selectValue){
                $selected = ' selected="selected"';
            }
            $out .= '<option value="'.$selectValue.'"'.$selected.'>'.$selectValue.'</option>';
        }
        $out .= '</select>';
        return $out;
    }
    
    protected function _getFilterSelectValues($columnName){
        if((isset($this->_filters[$columnName])) && (null !== $this->_filters[$columnName]->selectValues)){
            if(is_array($this->_filters[$columnName]->selectValues)){
                return $this->_filters[$columnName]->selectValues;
            }
        }
        $fetchResult    = $this->fetchResult;
        $values         = array();
        foreach($fetchResult as $row){
            if(isset($row[$columnName])){
                $values[] = $row[$columnName];
            }
        }
        $values = array_unique($values);
        sort($values);
        return $values;
    }
    
    public function getFilterValues(){
        $mvcRequest = Registry::get('RedMVC_Request');
        $params = $mvcRequest->getParams();
        $filters = array();
        $preString = 'filter'.$this->id.'_';
        $preStringLen = strlen($preString);
        foreach($params as $param => $value){
            if(strncmp($param, $preString, $preStringLen) === 0){
                if(trim($value) != ''){
                    $filters[substr($param, $preStringLen)] = $value;
                }
            }
        }
        return $filters;
    }
    
    public function insertFilterSql($sql, array $filters){
        if(count($filters) == 0){
            return $sql;
        }
        $this->_filterEnabled = true;
        $filterSql = ' WHERE ';
        if(stristr($sql, 'where') !== FALSE){
            $filterSql = ' AND ';
        }  
        $sql .= $filterSql;
        $likes = array();
        foreach($filters as $filter => $value){
            if(!get_magic_quotes_gpc()){
                $value = addslashes($value);
            }
            $value      = str_replace('%', '', $value);
            $value      = urldecode($value);
            $condition  = '';
            if($this->_filters[$filter]->sqlType === DataTable\Filter::SQL_TYPE_LIKE){
                $condition = "LIKE '%$value%'";
            }
            else if($this->_filters[$filter]->sqlType === DataTable\Filter::SQL_TYPE_EQUAL){
                $condition = "= '$value'";
            }
            else if($this->_filters[$filter]->sqlType === DataTable\Filter::SQL_TYPE_GREATER_THAN){
                $condition = "> '$value'";
            }
            else if($this->_filters[$filter]->sqlType === DataTable\Filter::SQL_TYPE_LESS_THAN){
                $condition = "< '$value'";
            }
            else if($this->_filters[$filter]->sqlType === DataTable\Filter::SQL_TYPE_GREATER_THAN_EQUAL){
                $condition = ">= '$value'";
            }
            else if($this->_filters[$filter]->sqlType === DataTable\Filter::SQL_TYPE_LESS_THAN_EQUAL){
                $condition = "<= '$value'";
            }            
            $likes[] = $this->_filters[$filter]->columnNameSql.' '.$condition;
        }
        $sql .= implode(' AND ', $likes);
        return $sql;
    }
    
    public function getHeadLinksArray(){
        $links = array();
        foreach($this->keys as $key){
            $links[$key] = $this->link($key.$this->getSortSymbol($key), $this->orderByUrl($key, "asc"));
        }
        return $links;
    }

    protected function parseRowTemplate($rowTemplateFunction){
        $result = '';
        $i = 1;
        foreach($this->fetchResult as $row=>$rowArray){
            $tempTemplate = call_user_func_array($rowTemplateFunction, array($rowArray, $i));

            // Toggle
            if($i == 1){$i = 2;}
            else{$i = 1;}

            $result .= $tempTemplate;
        }
        return $result;
    }

    function getPageNaviStandard(){
        $uiClass = '';
        if($this->_uiEnabled){
            $uiClass = ' arrow ui-state-hover ui-corner-all';
        }
        $first  = $this->link($this->firstSymbol, $this->pageUrl(0), '', '', 'first'.$uiClass);
        $prev   = $this->link($this->prevSymbol, $this->prevPageUrl(), '', '', 'prev'.$uiClass);
        $next   = $this->link($this->nextSymbol, $this->nextPageUrl(), '', '', 'next'.$uiClass);
        $last   = $this->link($this->lastSymbol, $this->lastPageUrl(), '', '', 'last'.$uiClass);

        $page       = $this->page + 1;
        $totalPages = $this->totalPages;
        $rows       = $this->numRows;
        
        $translate = Registry::get('RedMVC_Translate');
        
        $uiClass = '';
        if($this->_uiEnabled){
            $uiClass = ' ui-toolbar ui-widget-header ui-corner-bl ui-corner-br ui-helper-clearfix';
        }
        if(null !== $this->perPage){
            $div = "<div class=\"dataTablePages".$uiClass."\">$first $prev <span class=\"display\">".sprintf($translate->_('DATATABLE_PAGE_NAVI_1'), $page, $totalPages)."</span> $next $last <span class=\"display\"> $rows ".$translate->_('DATATABLE_PAGE_NAVI_2')."</span><div class='clear'></div></div>";
        }
        else{
            $div = "<div class=\"dataTablePages".$uiClass."\"><span class=\"display\">$rows ".$translate->_('DATATABLE_PAGE_NAVI_2')."</span><div class='clear'></div></div>";
        }
        return $div;
    }

    function getPageNaviStandard2($abstand = 5, $onlyNumbers = false){
        $first  = $this->link($this->firstSymbol, $this->pageUrl(0), '', 'font-size:8pt;');
        $prev   = $this->link($this->prevSymbol, $this->prevPageUrl(), '', 'font-size:8pt;');
        $next   = $this->link($this->nextSymbol, $this->nextPageUrl(), '', 'font-size:8pt;');
        $last   = $this->link($this->lastSymbol, $this->lastPageUrl(), '', 'font-size:8pt;');

        // Rechnen
        $seiten 	= $this->numRows / $this->perPage;
		$seiten 	= ceil($seiten)-1;
		$beginn 	= $this->page - $abstand;
		$ende 		= $this->page + $abstand;
		if($ende > $seiten) { $dif = -$seiten + $ende; $ende = $seiten; $beginn = $beginn - $dif; }
		if($beginn < 0) 	{ $dif = -$beginn; $ende = $ende + $dif; $beginn = 0; }
		if($ende > $seiten) { $ende = $seiten; }
		$beginn 	= floor($beginn);
		$ende 		= floor($ende);

        // Schleife
        $r = "";
        for($i=$beginn;$i<=$ende;$i++) {
            $r .= $this->link(($i+1), $this->pageUrl($i)).'&nbsp;';
        }
        $r .= "";

        // Pre/Post
        $pre = ($beginn > 0) ? '.. ' : '';
        $pst = ($ende < $this->totalPages-1) ? '..' : '';
        $r = $pre.$r.$pst;

        if($onlyNumbers){
            return $r;
        }

        $page       = $this->page + 1;
        $totalPages = $this->totalPages;
        $rows       = $this->numRows;
        
        $translate = Registry::get('RedMVC_Translate');

        $r = "<div>$first $prev $r $next $last</div>";
        $r .= "<div style='font-size:8pt;'>".sprintf($translate->_('DATATABLE_PAGE_NAVI_1'), $page, $totalPages)." - $rows ".$translate->_('DATATABLE_PAGE_NAVI_2')."</div>";
        
        return $r;
    }

    function getPerPageNavi(){
        $uiClass    = '';
        $seperator  = ' | ';
        if($this->_uiEnabled){
            $uiClass = ' perPage ui-state-hover ui-corner-all';
            $seperator = '&nbsp;';
        }
    
        $prePage5   = $this->link('5', $this->perPageUrl(5), '', '', $uiClass);
        $prePage10  = $this->link('10', $this->perPageUrl(10), '', '', $uiClass);
        $prePage20  = $this->link('20', $this->perPageUrl(20), '', '', $uiClass);
        $prePage50  = $this->link('50', $this->perPageUrl(50), '', '', $uiClass);
        
        $translate = Registry::get('RedMVC_Translate');

        return "<div class=\"dataTablePerPage\">".$translate->_('DATATABLE_PER_PAGE_NAVI')." $prePage5$seperator$prePage10$seperator$prePage20$seperator$prePage50</div>";
    }

    function fetchSql($sql){
        if(!$this->fetchCallback){
            $this->error("Keine Fetch Callback Funktion angegeben!");
            return Array();
        }
        $callbackReturn = call_user_func_array($this->fetchCallback, Array($sql));
        if(!$callbackReturn || !is_array($callbackReturn)){
            $this->error("Callback Return ist kein Array: ".$callbackReturn);
            return Array();
        }
        $this->fetchResult = $callbackReturn;
        return $callbackReturn;
    }

    function numRowsSql($sql){
        if(!$this->numRowsCallback){
            $this->error("Keine NumRows Callback Funktion angegeben!");
            return 0;
        }
        $callbackReturn = call_user_func_array($this->numRowsCallback, Array($sql));
        if(!$callbackReturn){
            #$this->error("NumRows Callback Return ist false");
            #return 0;
            $callbackReturn = 0;
        }
        $this->numRows = $callbackReturn;
        return $callbackReturn;
    }

    protected function getKeys(){
        if(isset($this->fetchResult[0])){
            $this->keys = array_keys($this->fetchResult[0]);
            return $this->keys;
        }
        return Array();
    }
    
    protected function _getKeysFromSql($sql){        
        if(strstr($sql, '*') !== false){
            return;
        }
        $sql = str_replace("\n", '', $sql);
        if(preg_match('/SELECT (.*?) FROM /i', $sql, $regs)){            
            $regs[1]    = $this->preFilter($regs[1]);            
            $result     = explode(",", $regs[1]);
            $result     = array_map(array($this, 'fieldFilter'), $result);
        }
        $this->keys = $result;        
    }
    
    public function preFilter($str){
        //filter out any () contents
        $str = preg_replace('/\(.*?\)/i', '', $str);
        return $str;
    }
    
    public function fieldFilter($str){
        $str = trim($str);    
        //filter out AS
        $str = preg_replace('/^.*as\s+/i', '', $str);
        //filter out spaces and back ticks and ][
        $str = trim($str, " ][`'");
        // filter out table aliases
        $fromPoint = strrchr($str, '.');
        if($fromPoint !== false){
            $str = substr($fromPoint, 1);
        }
        return $str;
    }

    // page, orderby, sort
    protected function getUrlValues(){
        $this->page     = $this->getUrlValue($this->pageName.$this->id, true, $this->pageDefault);
        $this->orderby  = $this->getUrlValue($this->orderbyName.$this->id, false, $this->orderbyDefault);
        $this->sort     = $this->getUrlValue($this->sortName.$this->id, false, $this->sortDefault);
        $this->perPage  = $this->getUrlValue($this->perPageName.$this->id, true, $this->perPageDefault);
        if($this->perPage == 0){
            $this->perPage = $this->perPageDefault;
        }
    }

    protected function getUrlValue($name, $parseInt = false, $standardValue = ''){
        $mvcRequest = Registry::get('RedMVC_Request');        
        $request = (isset($mvcRequest->$name)) ? $mvcRequest->$name : false;
        if(!$request){
            return $standardValue;
        }
        if($parseInt){
            $request = intval($request, 10);
        }
        return $request;
    }

    function nextPageUrl(){
        $nextPage = $this->page + 1;
        if($nextPage > $this->totalPages-1){return '';}
        return $this->pageUrl(min($this->totalPages-1, $nextPage));
    }

    function prevPageUrl(){
        $prevPage = $this->page - 1;
        if($prevPage < 0){return '';}
        return $this->pageUrl(max(0, $prevPage));
    }

    function lastPageUrl(){
        return $this->pageUrl($this->totalPages-1);
    }

    function pageUrl($page){
        if($page == $this->page || $this->page == -1){return '';}
        return Controller\Front::selfUrl(
            array($this->pageName.$this->id),
            array($this->pageName.$this->id=>$page)
        );
    }

    function blankPageUrl(){
        return Controller\Front::selfUrl(
            array($this->pageName.$this->id),
            array($this->pageName.$this->id=>'')
        );
    }

    function orderByUrl($key, $sort = "asc", $sortToggle = true){
        if($sortToggle){
            if($key == $this->orderby){
                $sort = ($this->sort == 'asc') ? 'desc' : 'asc';
            }
        }
        else if($key == $this->orderby && $this->sort == $sort){
            return '';
        }        
        return Controller\Front::selfUrl(
            array($this->orderbyName.$this->id, $this->sortName.$this->id),
            array($this->orderbyName.$this->id=>$key, $this->sortName.$this->id=>$sort)
        );
    }

    function getSortSymbol($key){
        if($key != $this->orderby){
            return '';
        }
        return ($this->sort == 'asc') ? $this->sortAscSymbol : $this->sortDescSymbol;
    }

    function perPageUrl($perPage = 20){
        if($perPage == $this->perPage){return '';}
        return Controller\Front::selfUrl(
            array($this->perPageName.$this->id),
            array($this->perPageName.$this->id=>$perPage)
        );
    }
    
    function filterResetUrl(){
        $mvcRequest = Registry::get('RedMVC_Request');
        $params     = $mvcRequest->getParams();
        $filters    = array();
        $preString  = 'filter'.$this->id.'_';
        $preStringLen = strlen($preString);
        foreach($params as $param => $value){
            if(strncmp($param, $preString, $preStringLen) === 0){
                $filters[] = $param;
            }
        }    
        return Controller\Front::selfUrl(
            $filters
        );
    }

    function link($name, $url, $title = '', $style = '', $class = ''){
        if($style != ''){$style = ' style="'.$style.'"';}
        if($title != ''){$title = ' title="'.$title.'"';}
        if($class != ''){$class = ' class="'.$class.'"';}
        if($url == ''){return '<span'.$title.''.$style.''.$class.'>'.$name.'</span>';}
        return '<a href="'.$url.'"'.$title.''.$style.''.$class.'>'.$name.'</a>';
    }

    protected function error($str){
        throw new \Exception('DataTable: '.$str);
    }
    
    public function addColumn($key, $value){
        $this->_extraColumns[$key] = $value;
    }
    
    public function addColumnCallback($columnName, $callbackFunction){
        $this->_columnCallbacks[$columnName] = $callbackFunction;
    }
    
    public function addFilter($columnName, $fieldType = DataTable\Filter::FIELD_TYPE_TEXT, $sqlType = DataTable\Filter::SQL_TYPE_LIKE, $columnNameSql = null){
        if(null === $columnNameSql){
            $columnNameSql = $columnName;
        }
        $filter = new DataTable\Filter();
        $filter->fieldType      = $fieldType;
        $filter->sqlType        = $sqlType;
        $filter->columnNameSql  = $columnNameSql;
        
        $this->_filters[$columnName] = $filter;
        return $filter;
    }
    
    public function addRow(array $rowArray){
        $this->_extraRows[] = $rowArray;
    }
    
    public function showExtraRows(){
        return '';
    }
    
    public function setTotalColumns(array $columns){
        foreach($columns as $column){
            $this->_totalColumns[$column] = 0;
        }        
    }
    
    public function showTotalRow(){
        if(count($this->fetchResult) == 0 || count($this->_totalColumns) == 0){
            return '';
        }
        
        $totalValues = array();
        
        foreach($this->fetchResult as $row=>$rowArray){            
            foreach($rowArray as $key=>$value){
                if(array_key_exists($key, $this->_totalColumns)){
                    $this->_totalColumns[$key] += $value;
                }
            }
        }
        
        $row = '<tr valign="top">';
        
        $uiClass = '';
        if($this->_uiEnabled){            
            $uiClass = ' ui-widget-content';            
            //$uiClass = ' ui-widget-header ui-state-hover';            
        }
        
        foreach($this->keys as $key){
            if(in_array($key, $this->_hiddenColumns)){
                continue;
            }
            if(array_key_exists($key, $this->_totalColumns)){
                $row .= '<td class="dataTable_tdTotal'.$uiClass.'">'.number_format($this->_totalColumns[$key], 2).'</td>';
            }
            else{
                $row .= '<td class="dataTable_tdTotal'.$uiClass.'">&nbsp;</td>';
            }            
        }
        
        // extra columns
        foreach($this->_extraColumns as $key){            
            $row .= '<td class="dataTable_tdTotal'.$uiClass.'">&nbsp;</td>';
        }
        
        $row .= '</tr>';
        
        return $row;        
    }
    
    public function hideColumns(array $columns){
        $this->_hiddenColumns = $columns;
    }
}